﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml;

namespace TeamFoundation.EventBus.Client
{
    /// <summary>
    /// Deserializes known schemas to strong type.
    /// </summary>
    public sealed class SerializationManager
    {
        /// <summary>
        /// Gets the singleton instance of the <see cref="SerializationManager"/>.
        /// </summary>
        public static SerializationManager Instance = new SerializationManager();

        private Dictionary<XName, XmlSerializer> _serializers = new Dictionary<XName, XmlSerializer>();

        /// <summary>
        /// Ctor.
        /// </summary>
        private SerializationManager()
        {
        }

        /// <summary>
        /// Adds the standard types to the serialization manager.
        /// </summary>
        public void AddStandardTypes()
        {
            Enroll(typeof(Events.AclChangedEvent));
            Enroll(typeof(Events.BranchMovedEvent));
            Enroll(typeof(Events.BuildCompletionEvent));
            Enroll(typeof(Events.BuildStatusChangeEvent));
            Enroll(typeof(Events.CheckinEvent));
            Enroll(typeof(Events.CommonStructureChangedEvent));
            Enroll(typeof(Events.DataChangedEvent));
            Enroll(typeof(Events.IdentityChangedEvent));
            Enroll(typeof(Events.IdentityCreatedEvent));
            Enroll(typeof(Events.IdentityDeletedEvent));
            Enroll(typeof(Events.MembershipChangedEvent));
            Enroll(typeof(Events.NodeCreatedEvent));
            Enroll(typeof(Events.NodeRenamedEvent));
            Enroll(typeof(Events.NodesDeletedEvent));
            Enroll(typeof(Events.ProjectCreatedEvent));
            Enroll(typeof(Events.ProjectDeletedEvent));
            Enroll(typeof(Events.TFSIdentity));
            Enroll(typeof(Events.WorkItemChangedEvent));
        }

        /// <summary>
        /// Enrolls a new type in the manager.
        /// </summary>
        /// <param name="t">The type to enroll.</param>
        public void Enroll(Type t)
        {
            string name = t.Name;
            string ns = string.Empty;

            XmlRootAttribute[] attrs = (XmlRootAttribute[])t.GetCustomAttributes(typeof(XmlRootAttribute), false);
            if (attrs.Length > 0)
            {
                if (!string.IsNullOrEmpty(attrs[0].ElementName))
                    name = attrs[0].ElementName;
                if (!string.IsNullOrEmpty(attrs[0].Namespace))
                    ns = attrs[0].Namespace;
            }

            XName newName = XName.Get(name, ns);
            lock (_serializers)
            {
                if (!_serializers.ContainsKey(newName))
                    _serializers.Add(newName, new XmlSerializer(t));
            }
        }

        /// <summary>
        /// Deserializes an <see cref="XElement"/>.
        /// </summary>
        /// <param name="element">The <see cref="XElement"/> to deserialize.</param>
        /// <returns>A <see cref="Object"/> or null if the deserializer could not be found.</returns>
        public object Deserialize(XElement element)
        {
            if (element == null)
                return null;

            XmlSerializer _serializer;
            lock (_serializers)
            {
                if (!_serializers.TryGetValue(element.Name, out _serializer))
                    return null;
            }

            using (XmlReader reader = element.CreateReader())
            {
                try
                {
                    return _serializer.Deserialize(reader);
                }
                catch
                {
                    return null;
                }
            }
        }


        /// <summary>
        /// Deserializes an <see cref="XElement"/> to a known type.
        /// </summary>
        /// <param name="element">The <see cref="XElement"/> to deserialize.</param>
        /// <typeparam name="T">The type to deserialize to.</typeparam>
        /// <returns>A <typeparamref name="T"/>or null if the deserializer could not be found.</returns>
        public T Deserialize<T>(XElement element)
            where T : class
        {
            return Deserialize(element) as T;
        }
    }
}
